﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#include "CInput.h"

namespace UX_STL
{
namespace UXTypesExt
{
	
/*
=================================================
	проверяет символ
=================================================
*/
	inline bool IsChar(char c, wchar w, char &cr, wchar &wr)
	{
		if ( c == 13 )
		{
			cr = 10;
			wr = 10;
			return true;
		}
		else
		if ( c >= ' ' or c == 9 )
		{
			cr = c;
			wr = w;
			return true;
		}
		return false;
	}

	inline bool IsBackspace(char c)
	{
		return c == 8;
	}

/*
=================================================
	конструктор
=================================================
*/
	CInput::CInput():
		_eParams(e_input_flags::UNKNOWN), _cLastChar(0), _wLastChar(0),
		_iDeltaWheel(0), _eTextInput(TIM_NONE)
	{
		_sText.Reserve( 255 );
		_sWText.Reserve( 255 );
		ZeroMem( _aKeys );
	}

/*
=================================================
	деструктор
=================================================
*/
	CInput::~CInput()
	{
	}
	
/*
=================================================
	устанавливает курсор на центр экрана
=================================================
*/
	void CInput::CursorToCenter() const
	{
		TPlatform::SetCursorPos( _pWindow->Descriptor().vPos + _pWindow->Descriptor().vSize.To<ivec2>() / 2 );
	}
	
/*
=================================================
	устанавливает курсор в заданную точку экрана
	(координаты нормализованны: 0.0 - 1.0)
=================================================
*/
	void CInput::SetCursorPos(const fvec2 &vPos) const
	{
		TPlatform::SetCursorPos( _pWindow->Descriptor().vPos + RoundToInt( _pWindow->Descriptor().vSize.To<fvec2>() * vPos ) );
	}

/*
=================================================
	инициализация системы ввода
=================================================
*/
	bool CInput::Init(CWindow *pWnd)
	{
		CHECK_ERR( pWnd != null );

		_pWindow = pWnd;

		pWnd->AddEventListener( this, e_wnd_event::KEY | e_wnd_event::MESSAGE |
									  e_wnd_event::MOUSE | e_wnd_event::MOUSE_KEY );
		return true;
	}
	
/*
=================================================
	деинициализация
=================================================
*/
	void CInput::Destroy()
	{
		if ( _pWindow != null )
			_pWindow->RemoveEventListener( this );

		//ClipCursor(false);

#	ifndef UX_MT_INPUT_DISABLE
		_sCSLockInput.Delete();
#	endif	// UX_MT_INPUT_DISABLE
	}


#ifndef UX_MT_INPUT_DISABLE
/*
=================================================
	для многопоточности: обработка ввода
=================================================
*/
	void CInput::Update()
	{
		_sCSLockInput.Enter();

		array< TWindowEvent >	a_events( _aEvents );

		_aEvents.ClearMem();

		_sCSLockInput.Leave();

		// reset states //
		_iDeltaWheel = 0;
		_sMDelta	 = fvec2();

		for (usize i = 0; i < a_events.Count(); ++i)
		{
			// KEYS //
			if ( a_events[i].eEventType == e_wnd_event::KEY )
			{
				if ( a_events[i].sEvent.sKey.bPressed )	_aKeys[ a_events[i].sEvent.sKey.eKey ] |= e_key_state::DOWN;
				else									_aKeys[ a_events[i].sEvent.sKey.eKey ]  = e_key_state::UP;
				
				if ( IsChar( a_events[i].sEvent.sKey.cChar, a_events[i].sEvent.sKey.wChar, _cLastChar, _wLastChar ) )
				{
					if ( IsTextInputA() )	_sText  << _cLastChar;
					if ( IsTextInputW() )	_sWText << _wLastChar;
				}
				else
				if ( IsBackspace( sEvent.sEvent.sKey.cChar ) )
				{
					if ( IsTextInputA() )	_sText.PopBack();
					if ( IsTextInputW() )	_sWText.PopBack();
				}
			}
			else

			// MOUSE BUTTONS //
			if ( a_events[i].eEventType == e_wnd_event::MOUSE_KEY )
			{
				if ( a_events[i].sEvent.sMouseKey.bButton1Changed )
					if ( a_events[i].sEvent.sMouseKey.bButton1Pressed )	_aKeys[ e_key::MOUSE_1 ] |= e_key_state::DOWN;
					else												_aKeys[ e_key::MOUSE_1 ]  = e_key_state::UP;
				
				if ( a_events[i].sEvent.sMouseKey.bButton2Changed )
					if ( a_events[i].sEvent.sMouseKey.bButton2Pressed )	_aKeys[ e_key::MOUSE_2 ] |= e_key_state::DOWN;
					else												_aKeys[ e_key::MOUSE_2 ]  = e_key_state::UP;
				
				if ( a_events[i].sEvent.sMouseKey.bButton3Changed )
					if ( a_events[i].sEvent.sMouseKey.bButton3Pressed )	_aKeys[ e_key::MOUSE_3 ] |= e_key_state::DOWN;
					else												_aKeys[ e_key::MOUSE_3 ]  = e_key_state::UP;
				
				if ( a_events[i].sEvent.sMouseKey.bButton4Changed )
					if ( a_events[i].sEvent.sMouseKey.bButton4Pressed )	_aKeys[ e_key::MOUSE_4 ] |= e_key_state::DOWN;
					else												_aKeys[ e_key::MOUSE_4 ]  = e_key_state::UP;
				
				if ( a_events[i].sEvent.sMouseKey.bButton5Changed )
					if ( a_events[i].sEvent.sMouseKey.bButton5Pressed )	_aKeys[ e_key::MOUSE_5 ] |= e_key_state::DOWN;
					else												_aKeys[ e_key::MOUSE_5 ]  = e_key_state::UP;

				_iDeltaWheel += a_events[i].sEvent.sMouseKey.iDeltaWheel;
			}
			else

			// MOUSE MOVE //
			if ( a_events[i].eEventType == e_wnd_event::MOUSE )
			{
				if ( a_events[i].sEvent.sMouse.bDelta )
				{
					_sMDelta.x	+= a_events[i].sEvent.sMouse.iX;
					_sMDelta.y	+= a_events[i].sEvent.sMouse.iY;
				}
				else
				{
					_sMPos.x	= a_events[i].sEvent.sMouse.iX;
					_sMPos.y	= a_events[i].sEvent.sMouse.iY;
				}
			}
		}
		
		if ( _iDeltaWheel != 0 )
		{
			_aKeys[ e_key::M_WHEEL_UP + (_iDeltaWheel < 0) ] |= e_key_state::DOWN;
			_aKeys[ e_key::M_WHEEL_UP + (_iDeltaWheel > 0) ]  = e_key_state::UP;
		}
		else
		{
			_aKeys[ e_key::M_WHEEL_DOWN ] = e_key_state::UP;
			_aKeys[ e_key::M_WHEEL_UP   ] = e_key_state::UP;
		}
	}

/*
=================================================
	сброс значений
=================================================
*/
	void CInput::Reset()
	{
		_cLastChar = 0;
		_wLastChar = 0;
	}

/*
=================================================
	обработка сообщений
=================================================
*/
	void CInput::ProcessMessage(const TWindowEvent &sEvent)
	{
		if ( EnumCmp( sEvent.eEventType, e_wnd_event::KEY | e_wnd_event::MOUSE | e_wnd_event::MOUSE_KEY ) )
		{
			_sCSLockInput.Enter();
			_aEvents.PushBack( sEvent );
			_sCSLockInput.Leave();
		}
		else

		if ( sEvent.eEventType == e_wnd_event::MESSAGE )
		{
			if ( sEvent.sEvent.sMsg.eMsg == TWindowEvent::TMessage::MOVE or
				 sEvent.sEvent.sMsg.eMsg == TWindowEvent::TMessage::MAXIMIZED or
				 sEvent.sEvent.sMsg.eMsg == TWindowEvent::TMessage::RESIZE or
				 sEvent.sEvent.sMsg.eMsg == TWindowEvent::TMessage::RESUME )
			{
				ClipCursor( true );
			}
			else

			if ( sEvent.sEvent.sMsg.eMsg == TWindowEvent::TMessage::MINIMIZED or
				 sEvent.sEvent.sMsg.eMsg == TWindowEvent::TMessage::PAUSE )
			{
				ClipCursor( false );
			}
		}
	}

#else	// UX_MT_INPUT_DISABLE

/*
=================================================
	обновить состояния
=================================================
*/
	void CInput::Update()
	{
		if ( _iDeltaWheel != 0 )
		{
			_aKeys[ e_key::M_WHEEL_UP + (_iDeltaWheel < 0) ] |= e_key_state::DOWN;
			_aKeys[ e_key::M_WHEEL_UP + (_iDeltaWheel > 0) ]  = e_key_state::UP;
		}
		else
		{
			_aKeys[ e_key::M_WHEEL_DOWN ] = e_key_state::UP;
			_aKeys[ e_key::M_WHEEL_UP   ] = e_key_state::UP;
		}
	}

/*
=================================================
	сброс значений
=================================================
*/
	void CInput::Reset()
	{
		_sMDelta	 = fvec2();
		_iDeltaWheel = 0;
		_cLastChar	 = 0;
		_wLastChar	 = 0;
	}

/*
=================================================
	обработка сообщений
=================================================
*/
	void CInput::ProcessMessage(const TWindowEvent &sEvent)
	{
		if ( EnumCmp( sEvent.eEventType, e_wnd_event::KEY | e_wnd_event::MOUSE | e_wnd_event::MOUSE_KEY ) )
		{
			// KEYS //
			if ( sEvent.eEventType == e_wnd_event::KEY )
			{
				if ( sEvent.sEvent.sKey.bPressed )	_aKeys[ sEvent.sEvent.sKey.eKey ] |= e_key_state::DOWN;
				else								_aKeys[ sEvent.sEvent.sKey.eKey ]  = e_key_state::UP;
				
				if ( IsChar( sEvent.sEvent.sKey.cChar, sEvent.sEvent.sKey.wChar, _cLastChar, _wLastChar ) )
				{
					if ( IsTextInputA() )	_sText  << _cLastChar;
					if ( IsTextInputW() )	_sWText << _wLastChar;
				}
				else
				if ( IsBackspace( sEvent.sEvent.sKey.cChar ) )
				{
					if ( IsTextInputA() )	_sText.PopBack();
					if ( IsTextInputW() )	_sWText.PopBack();
				}
			}
			else

			// MOUSE BUTTONS //
			if ( sEvent.eEventType == e_wnd_event::MOUSE_KEY )
			{
				_iDeltaWheel += sEvent.sEvent.sMouseKey.iDeltaWheel;

				if ( sEvent.sEvent.sMouseKey.bButton1Changed )
					if ( sEvent.sEvent.sMouseKey.bButton1Pressed )	_aKeys[ e_key::MOUSE_1 ] |= e_key_state::DOWN;
					else											_aKeys[ e_key::MOUSE_1 ]  = e_key_state::UP;
				
				if ( sEvent.sEvent.sMouseKey.bButton2Changed )
					if ( sEvent.sEvent.sMouseKey.bButton2Pressed )	_aKeys[ e_key::MOUSE_2 ] |= e_key_state::DOWN;
					else											_aKeys[ e_key::MOUSE_2 ]  = e_key_state::UP;
				
				if ( sEvent.sEvent.sMouseKey.bButton3Changed )
					if ( sEvent.sEvent.sMouseKey.bButton3Pressed )	_aKeys[ e_key::MOUSE_3 ] |= e_key_state::DOWN;
					else											_aKeys[ e_key::MOUSE_3 ]  = e_key_state::UP;
				
				if ( sEvent.sEvent.sMouseKey.bButton4Changed )
					if ( sEvent.sEvent.sMouseKey.bButton4Pressed )	_aKeys[ e_key::MOUSE_4 ] |= e_key_state::DOWN;
					else											_aKeys[ e_key::MOUSE_4 ]  = e_key_state::UP;
				
				if ( sEvent.sEvent.sMouseKey.bButton5Changed )
					if ( sEvent.sEvent.sMouseKey.bButton5Pressed )	_aKeys[ e_key::MOUSE_5 ] |= e_key_state::DOWN;
					else											_aKeys[ e_key::MOUSE_5 ]  = e_key_state::UP;
			}
			else

			// MOUSE MOVE //
			if ( sEvent.eEventType == e_wnd_event::MOUSE )
			{
				if ( sEvent.sEvent.sMouse.bDelta )
				{
					_sMDelta.x	+= sEvent.sEvent.sMouse.iX;
					_sMDelta.y	+= sEvent.sEvent.sMouse.iY;
				}
				else
				{
					_sMPos.x	= sEvent.sEvent.sMouse.iX;
					_sMPos.y	= sEvent.sEvent.sMouse.iY;
				}
			}
		}
	}
#endif	// !UX_MT_INPUT_DISABLE
	


#ifdef PLATFORM_WINDOWS
/*
=================================================
	
=================================================
*/
	bool CInput::OS_MouseButtonState(e_key::type eMB, bool bPressed) const
	{
		winapi::tagINPUT	s_input = {0};

		s_input.type = winapi::INPUT_MOUSE;

		if ( eMB >= e_key::MOUSE_4 )
		{
			s_input.mi.dwFlags	 = (bPressed+1) << 8;			// 0x080 or 0x100
			s_input.mi.mouseData = eMB - e_key::MOUSE_4 + 1;		// 0x1 or 0x2
		}
		else
		{
			s_input.mi.dwFlags	= ( 2 << (eMB - e_key::MOUSE_1) ) << (1-bPressed);
		}

		return winapi::SendInput( 1, &s_input, sizeof(s_input) ) == 1;
	}

/*
=================================================
	
=================================================
*/
	bool CInput::OS_MouseMove(const ivec2 &vDelta) const
	{
		winapi::tagINPUT	s_input = {0};

		s_input.type		= winapi::INPUT_MOUSE;
		s_input.mi.dx		= vDelta.x;
		s_input.mi.dy		= vDelta.y;
		s_input.mi.dwFlags	= winapi::MOUSEEVENTF_MOVE;
		
		return winapi::SendInput( 1, &s_input, sizeof(s_input) ) == 1;
	}

/*
=================================================
	
=================================================
*/
	bool CInput::OS_MouseWheelState(int iDelta) const
	{
		winapi::tagINPUT	s_input = {0};

		s_input.type		 = winapi::INPUT_MOUSE;
		s_input.mi.dwFlags	 = winapi::MOUSEEVENTF_WHEEL;
		s_input.mi.mouseData = winapi::WHEEL_DELTA * iDelta;

		return winapi::SendInput( 1, &s_input, sizeof(s_input) ) == 1;
	}

/*
=================================================
	
=================================================
*/
	bool CInput::OS_KeyState(e_key::type eKey, bool bPressed) const
	{
		winapi::tagINPUT	s_input = {0};

		s_input.type		= winapi::INPUT_KEYBOARD;
		s_input.ki.wVk		= eKey > e_key::right_offset ? eKey - e_key::right_offset : eKey;
		s_input.ki.dwFlags	= bPressed ? 0 : winapi::KEYEVENTF_KEYUP;

		return winapi::SendInput( 1, &s_input, sizeof(s_input) ) == 1;
	}
	
/*
=================================================
	
=================================================
*/
	bool CInput::OS_SetWChar(wchar c) const
	{
		winapi::tagINPUT	s_input = {0};

		s_input.type		= winapi::INPUT_KEYBOARD;
		s_input.ki.wVk		= 0;
		s_input.ki.dwFlags	= winapi::KEYEVENTF_UNICODE;
		s_input.ki.wScan	= c;

		return winapi::SendInput( 1, &s_input, sizeof(s_input) ) == 1;
	}

#endif	// PLATFORM_WINDOWS

//-------------------------------------------------------------------

}	// UXTypesExt
}	// UX_STL
